﻿using System;
using System.IO;
using System.Reflection;
using System.ServiceModel;

using SsrsRenderStudio.Wcf.ServiceContract;
using SsrsRenderStudio.Wcf.DataContract;
using SsrsRenderStudio.Configuration;
using SsrsRenderStudio.Renderer;
using SsrsRenderStudio.Shared;
using SsrsRenderStudio.Renderer.Output.Version2013;
using SsrsRenderStudio.KnownTypesAndModels;
using SsrsRenderStudio.Wcf.Services;

namespace SsrsRenderStudio.Wcf.ServiceImpl
{
    // http://www.dotnetfunda.com/articles/article912-3-ways-to-do-wcf-instance-management-per-call-per-session-and-single-.aspx

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Single)]
    public class RenderStudioUserService : IRenderStudioUserService, IDisposable
    {
        private RenderingEngine _renderEngine;                  // rendering engine to process the RDL against a REX.
        private string _renderEngineId 
        { get { return _renderEngine.EngineId.ToString(); } }   // the ID of the rendering engine within context

        private bool _isFaulted = false;

        /// <summary>
        /// Cleanup.
        /// </summary>
        public void Dispose()
        {
            //TODO shutdown rendering engine
            Logger.Log("80002", _renderEngine.EngineId.ToString() );
            Logger.EndSession();
            _renderEngine = null;
        }

        /// <summary>
        /// Default constructor.
        /// </summary>
        public RenderStudioUserService()
        {
            _renderEngine = new RenderingEngine();

            Logger.StartSession(_renderEngineId);

            //hack: looks like this only checks for the validity of application.config. 
            //  That's not much of a check since the host has already opened itself.
            if (ConfigurationManager.ApplicationConfigFromConfigPointer == null)
            {
                _isFaulted = true;
                return;
            }

            Logger.StartSection("userservice_enginector");
            
            //Logger.Log(EnumLoggingSeverity.DEBUG, "appid: " + AppDomain.CurrentDomain.Id.ToString() + "  thr: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
            Logger.LogSection("80003", "userservice_enginector");
        }

        public ExecutionResult PrintReport(ExecutionContext context)
        {
            try
            {
                if (_isFaulted) throw new Exception("Server Configuration Is Broken.");

                Logger.StartSection("TRACE_SERVICE_PRINT_REPORT");

                Logger.Log("60009", "Print", context.FriendlyReportName, context.Destination);
                Logger.Log("60010", string.Join(";", context.ClientInformation));

                PrinterInfo10 printerInfo = null;
                ReportConfig10 reportInfo = null;
                RenderStudioServerConfig10 serverInfo = null;
                ReportServerConfig10 reportServerInfo = null;

                RetrieveSettingsFromConfiguration(context.FriendlyReportName,
                    context.Destination,
                    out printerInfo,
                    out reportInfo,
                    out serverInfo,
                    out reportServerInfo);

                if (printerInfo == null || reportInfo == null || serverInfo == null || reportServerInfo == null)
                {
                    EnumServiceSideException ex1 = EnumServiceSideException.SERVICE_FAILED_TO_GET_CONFIGURATION;
                    string ex2 = "Aborting, invalid printer, report or server information.";
                    Logger.LogSection("10000", "TRACE_SERVICE_PRINT_REPORT", ex2);
                    return new ExecutionResult(WasExecutionSuccessfulAndLogExceptions(ex1, ex2), ex1, ex2, Guid.Empty, DateTime.MinValue);
                }

                prepareRenderEngine(reportInfo.Url, reportServerInfo.RexUrl, EnumSupportedRenderer.IMAGE, printerInfo.DpiX, printerInfo.DpiY);
                RenderExecutionInformation renderExecutionInfo = renderReport(serverInfo, reportServerInfo, context.ReportParameters);

                Guid processedReportId = Guid.Empty;

                if (renderExecutionInfo.ServerException == EnumServiceSideException.NONE)
                {
                    if (serverInfo.IsDebug)
                        writeReportToDebugFile(context, renderExecutionInfo.RenderedReport, processedReportId, "TIF");

                    // send to printer
                    SsrsRenderStudio.Renderer.Output.Version2013.PrinterOutputParameters printerParameters = new Renderer.Output.Version2013.PrinterOutputParameters
                    {
                        RenderedReportBuffer = renderExecutionInfo.RenderedReport,
                        DeviceContextRetrievalFunction = DeviceContextCache.Get,
                        PageSettings = reportInfo.OverridePageSettings == true ? reportInfo.PageSettings : _renderEngine.ReportPageSettings,
                        PrinterInformation = printerInfo
                    };

                    processedReportId = processOutput(new PrinterOutputSpooler(printerParameters), "Printer");
                }

                Logger.LogSection("80007", "TRACE_SERVICE_PRINT_REPORT");

                return new ExecutionResult(WasExecutionSuccessfulAndLogExceptions(renderExecutionInfo.ServerException, _renderEngine.ThrownException),
                    renderExecutionInfo.ServerException,
                    _renderEngine.ThrownException,
                    processedReportId,
                    DateTime.MinValue);
            }
            catch (Exception ex)
            {
                Logger.LogSection("80007", "TRACE_SERVICE_PRINT_REPORT");

                return new ExecutionResult(WasExecutionSuccessfulAndLogExceptions(EnumServiceSideException.SERVICE_CRITICAL_SERVICE_FAULT, ex.Message),
                    EnumServiceSideException.SERVICE_CRITICAL_SERVICE_FAULT,
                    ex.Message,
                    Guid.Empty,
                    DateTime.MinValue);
            }
        }

        public ExecutionResult EmailReport(ExecutionContext context)
        {
            try
            {
                if (_isFaulted) throw new Exception("Server Configuration Is Broken.");

                Logger.Log("60009", "Email", context.FriendlyReportName, context.Destination);
                Logger.Log("60010", string.Join(";", context.ClientInformation));

                PrinterInfo10 printerInfo = null;
                ReportConfig10 reportInfo = null;
                RenderStudioServerConfig10 serverInfo = null;
                ReportServerConfig10 reportServerInfo = null;

                RetrieveSettingsFromConfiguration(context.FriendlyReportName,
                    context.Destination,
                    out printerInfo,
                    out reportInfo,
                    out serverInfo,
                    out reportServerInfo);

                if (reportInfo == null || serverInfo == null || reportServerInfo == null)
                {
                    EnumServiceSideException ex1 = EnumServiceSideException.SERVICE_FAILED_TO_GET_CONFIGURATION;
                    string ex2 = "Aborting, invalid report or server information.";
                    Logger.Log("10000", ex2, "n/a");
                    return new ExecutionResult(WasExecutionSuccessfulAndLogExceptions(ex1, ex2), ex1, ex2, Guid.Empty, DateTime.MinValue);
                }

                prepareRenderEngine(reportInfo.Url, reportServerInfo.RexUrl, EnumSupportedRenderer.PDF);
                RenderExecutionInformation renderExecutionInfo = renderReport(serverInfo, reportServerInfo, context.ReportParameters);

                Guid processedReportId = Guid.Empty;

                if (renderExecutionInfo.ServerException == EnumServiceSideException.NONE)
                {
                    if (serverInfo.IsDebug)
                        writeReportToDebugFile(context, renderExecutionInfo.RenderedReport, processedReportId, "PDF");

                    System.Collections.Generic.List<System.Net.Mail.MailAddress> recipients = new System.Collections.Generic.List<System.Net.Mail.MailAddress>();
                    recipients.Add(new System.Net.Mail.MailAddress(context.Destination));
                    SsrsRenderStudio.Renderer.Output.Version2013.EmailOutputParameters emailParameters = new Renderer.Output.Version2013.EmailOutputParameters
                    {
                        RenderedReportBuffer = renderExecutionInfo.RenderedReport,
                        Recipients = recipients,
                        MimeContentType = renderExecutionInfo.Encoding,
                        FileExtension = "PDF",
                    };
                    ;

                    processedReportId = processOutput(new EmailOutput(emailParameters), "Emailing");

                    //todo: implement URL link to reprocess report
                    //email.AppendBody("This report is available for re-processing as " + reportId.ToString() + " until " + ReportCache.WhenWillReportExpire(reportId).ToString());
                }

                return new ExecutionResult(WasExecutionSuccessfulAndLogExceptions(renderExecutionInfo.ServerException, _renderEngine.ThrownException), renderExecutionInfo.ServerException, _renderEngine.ThrownException, processedReportId, DateTime.MinValue);
            }
            catch (Exception ex)
            {
                return new ExecutionResult(WasExecutionSuccessfulAndLogExceptions(EnumServiceSideException.SERVICE_CRITICAL_SERVICE_FAULT, ex.Message),
                    EnumServiceSideException.SERVICE_CRITICAL_SERVICE_FAULT,
                    ex.Message,
                    Guid.Empty,
                    DateTime.MinValue);
            }
        }

        /// <summary>
        /// Reprocess report from cache.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public bool ReprocessReport(Guid id)
        {
            Logger.Log("60013", id.ToString());

            Output o = ReportCache.Retrieve(id);
            o.Process();
            o = null;

            return true;

            /*
            return false;

            Output output = null;
            ProcessedReport rpt = null;
            
            
            IRenderStudioSystemService sysproxy = ConfigurationManager.CreateRenderStudioClientProxy<IRenderStudioSystemService>(TALK_TO_SYSTEM_SERVICE_IPC);
            rpt = sysproxy.GetProcessedReportInfo(id);
            ConfigurationManager.CloseRenderStudioClientChannel(sysproxy);
            sysproxy = null;
            
            
            switch (rpt.OutputType)
            {
                case EnumOutputType.PRINTER:
                    output = new PrinterOutput(rpt.RenderedBuffer, rpt.Destination);
                    //TODO: need to set printer settings
                    break;
                case EnumOutputType.EMAIL:
                    output = new EmailOutput(rpt.RenderedBuffer, rpt.Destination, rpt.Extension, rpt.EncodingType);
                    break;
                default:
                    output = new NullOutput();
                    break;
            }

            output.Process();
            output = null;

            return true;
            */
        }

        /// <summary>
        /// Prepares rendering engine for report execution with correct format.
        /// </summary>
        private void prepareRenderEngine(string reportUrl, string rexUrl, EnumSupportedRenderer renderFormat, int dpiX = 0, int dpiY = 0)
        {
            Logger.StartSection("userservice_prepengine");
            _renderEngine.ChangeContext(reportUrl, null, rexUrl);
            _renderEngine.SetRenderFormat(renderFormat, dpiX, dpiY);
            Logger.LogSection("80004", "userservice_prepengine");
        }

        private Guid processOutput(Output output, string processingType, bool addToCache = true)
        {
            Logger.StartSection("userservice_process");
            output.Process();
            Logger.LogSection("80006", "userservice_process", processingType);

            return (addToCache) ? ReportCache.Add(output) : Guid.Empty;
        }

        private void writeReportToDebugFile(ExecutionContext context, byte[][] renderedReport, Guid reportId, string extension)
        {
            try
            {
                FileInfo finfo = new FileInfo(@Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\debug\" + context.FriendlyReportName + "_contextId_" + reportId.ToString() + "." + extension);
                FileOutputParameters fileParameters = new Renderer.Output.Version2013.FileOutputParameters { RenderedReportBuffer = renderedReport, DestinationFile = finfo };
                processOutput(new FileOutput(fileParameters), "Filing", false);
            }
            catch (Exception ex)
            {
                Logger.Log("30002", ex.Message);
            }
        }

        private void RetrieveSettingsFromConfiguration(string friendlyReportName, string destination, out PrinterInfo10 printerInfo, out ReportConfig10 reportInfo, out RenderStudioServerConfig10 serverInfo, out ReportServerConfig10 reportServerInfo)
        {
            //TODO: bypass printer if destination regex email
            printerInfo = ConfigurationManager.PrinterSettingsFromFriendlyName10(destination);
            reportInfo = ConfigurationManager.ReportSettingsFromFriendlyName10(friendlyReportName);
            serverInfo = ConfigurationManager.ServerInfoFromFriendlyName10(ConfigurationManager.ApplicationConfigFromConfigPointer.ApplicationName);
            reportServerInfo = null;

            if (serverInfo != null) reportServerInfo = ConfigurationManager.ReportServerSettingsFromFriendlyName(serverInfo.PreferredReportServer);
        }

        /// <summary>
        /// Determine if rendering was successful, also send any exceptions to logger.
        /// </summary>
        private bool WasExecutionSuccessfulAndLogExceptions(EnumServiceSideException ex1, string ex2)
        {
            bool wasSuccess = false;
            if (ex1 == EnumServiceSideException.NONE && string.IsNullOrEmpty(ex2)) 
                wasSuccess = true;

            if (!wasSuccess)
                Logger.Log("10002",  ex1.ToString(), ex2);

            Logger.Log("60012",wasSuccess.ToString());
            
            return wasSuccess;
        }

        #region Render Execution

        private class RenderExecutionInformation
        {
            public byte[][] RenderedReport = null;
            public string Extension = string.Empty;
            public string Encoding = string.Empty;
            public EnumServiceSideException ServerException = EnumServiceSideException.RENDERER_REX_DOWN;
        }

        private RenderExecutionInformation renderReport(RenderStudioServerConfig10 serverInfo, ReportServerConfig10 reportServerInfo, string[] reportParameters)
        {
            RenderExecutionInformation executionInfo = new RenderExecutionInformation();

            // do until exception changes or no more report servers are available
            while (serverInfo.RexRecoveryOnException.Contains(executionInfo.ServerException) && !(reportServerInfo == null))
            {
                Logger.StartSection("TRACE_RENDERER_RENDER");
                executionInfo.ServerException = _renderEngine.RenderReport(out executionInfo.RenderedReport, out executionInfo.Extension, out executionInfo.Encoding, new ParameterHash(reportParameters).Table);
                Logger.LogSection("80009", "TRACE_RENDERER_RENDER");

                // PREVENT SSRS REX DOMAIN RECYCLE TIME DELAYS
                // if the render completed successfully then add this REX to the servers we want to keep alive
                if (executionInfo.ServerException == EnumServiceSideException.NONE)
                    RexKeepAlive.Add(reportServerInfo);

                // if in recovery mode then move on to the next REX
                // recovery mode means that the render engine exception returned is one that shoudl trigger a recovery
                if (serverInfo.RexRecoveryOnException.Contains(executionInfo.ServerException))
                {
                    Logger.Log("10001",executionInfo.ServerException.ToString());

                    // this will eventually return null when no more entries are available
                    reportServerInfo = ConfigurationManager.NextReportServerSettings(reportServerInfo.FriendlyName, serverInfo.PreferredReportServerGroup, serverInfo.PreferredReportServerGroupOnly);

                    if (reportServerInfo == null)
                    {
                        executionInfo.ServerException = EnumServiceSideException.RENDERER_REX_UNABLE_TO_RECOVER;
                        Logger.Log("50000");
                    }
                    else
                    {
                        // point the render engine at a different REX, and resubmit job
                        _renderEngine.ChangeContext(null, null, reportServerInfo.RexUrl);
                    }
                }
            }

            Logger.Log("60011", executionInfo.ServerException.ToString());

            return executionInfo;
        }

        #endregion
    }
}
